package com.sugar.subscribe.course.module.business.service.withdrawrecord;

import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.sugar.subscribe.course.framework.common.enums.UserTypeEnum;
import com.sugar.subscribe.course.framework.common.util.json.JsonUtils;
import com.sugar.subscribe.course.framework.common.util.object.ObjectUtils;
import com.sugar.subscribe.course.module.business.controller.app.withdrawrecord.vo.AppWithdrawRecordRespVO;
import com.sugar.subscribe.course.module.business.dal.redis.objlock.ObjectLockRedisDAO;
import com.sugar.subscribe.course.module.business.service.coachwallet.CoachWalletService;
import com.sugar.subscribe.course.module.business.service.user.BizUserService;
import com.sugar.subscribe.course.module.business.util.AmountUtil;
import com.sugar.subscribe.course.module.pay.api.transfer.PayTransferApi;
import com.sugar.subscribe.course.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
import com.sugar.subscribe.course.module.pay.api.transfer.dto.PayTransferCreateRespDTO;
import com.sugar.subscribe.course.module.pay.api.transfer.dto.PayTransferRespDTO;
import com.sugar.subscribe.course.module.pay.enums.transfer.PayTransferStatusEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;

import com.sugar.subscribe.course.module.business.controller.admin.withdrawrecord.vo.*;
import com.sugar.subscribe.course.module.business.dal.dataobject.withdrawrecord.WithdrawRecordDO;
import com.sugar.subscribe.course.framework.common.pojo.PageResult;
import com.sugar.subscribe.course.framework.common.pojo.PageParam;

import com.sugar.subscribe.course.module.business.dal.mysql.withdrawrecord.WithdrawRecordMapper;

import static com.sugar.subscribe.course.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.sugar.subscribe.course.framework.common.util.servlet.ServletUtils.getClientIP;
import static com.sugar.subscribe.course.module.business.enums.ErrorCodeConstants.*;
import static com.sugar.subscribe.course.module.business.enums.order.WithdrawStatus.*;
import static com.sugar.subscribe.course.module.business.service.order.OrderServiceImpl.APP_KEY;
import static com.sugar.subscribe.course.module.pay.enums.PayChannelEnum.WX_LITE;
import static com.sugar.subscribe.course.module.pay.enums.ErrorCodeConstants.PAY_TRANSFER_NOT_FOUND;

/**
 * 提现记录 Service 实现类
 *
 * @author Ysugar
 */
@Slf4j
@Service
@Validated
public class WithdrawRecordServiceImpl implements WithdrawRecordService {

    @Resource
    private WithdrawRecordMapper withdrawRecordMapper;
    @Resource
    private CoachWalletService coachWalletService;

    @Resource
    private BizUserService userService;

    @Resource
    private PayTransferApi payTransferApi;

    @Resource
    private ObjectLockRedisDAO redisLockDAO;

    @Override
    public void updateWithdrawRecord(WithdrawRecordSaveReqVO updateReqVO) {
        Long id = updateReqVO.getId();
        Integer status = updateReqVO.getStatus();

        redisLockDAO.lock(id, 15000L, () -> {
            WithdrawRecordDO withdrawRecordDO = validateWithdrawRecordExists(id);
            // 若是待审核状态再执行，防止重复审核
            if (withdrawRecordDO.getStatus().equals(AUDITING.getStatus())){
                // 若是提现失败，则直接记录失败
                if (status.equals(AUDIT_FAIL.getStatus())){
                    if (StrUtil.isBlank(updateReqVO.getFailCause()))
                        throw exception(WITHDRAW_RECORD_REFUSE_CAUSE_NOT_EMPTY);

                    // 退回用户提现的金额
                    coachWalletService.updateCoachWallet(withdrawRecordDO.getCoachId(), true, withdrawRecordDO.getAmount());

                    // 更新提现申请状态
                    withdrawRecordMapper.update(WithdrawRecordDO::getId, id,
                            WithdrawRecordDO::getStatus, AUDIT_FAIL.getStatus(),
                            WithdrawRecordDO::getFailCause, updateReqVO.getFailCause());
                }else if (status.equals(AUDIT_SUCCESS.getStatus())){
                    // 获取教练用户
                    String openId = userService.getUserOpenIdByCoachId(withdrawRecordDO.getCoachId());

                    PayTransferCreateReqDTO transferReqDTO = new PayTransferCreateReqDTO();

                    transferReqDTO.setAppKey(APP_KEY)
                            .setUserIp(getClientIP())
                            .setUserId(withdrawRecordDO.getCoachId())
                            .setUserType(UserTypeEnum.MEMBER.getValue())
                            .setMerchantTransferId(String.valueOf(withdrawRecordDO.getId()))
                            .setPrice(withdrawRecordDO.getAmount())
                            .setSubject("余额提现")
                            .setUserAccount(openId)
                            //.setUserName("")
                            .setChannelCode(WX_LITE.getCode())
                            // 特殊：微信需要有报备信息
                            .setChannelExtras(PayTransferCreateReqDTO.buildWeiXinChannelExtra1000("余额提现", "余额提现"));

                    // 创建提现申请单
                    PayTransferCreateRespDTO transferRespDTO = payTransferApi.createTransfer(transferReqDTO);

                    // 更新提现申请状态
                    withdrawRecordMapper.update(WithdrawRecordDO::getId, id,
                            WithdrawRecordDO::getStatus, AUDIT_SUCCESS.getStatus(),
                            WithdrawRecordDO::getPayTransferId, transferRespDTO.getId());
                }
            }
        });
    }

    @Override
    public void deleteWithdrawRecord(Long id) {
        // 校验存在
        validateWithdrawRecordExists(id);
        // 删除
        withdrawRecordMapper.deleteById(id);
    }

    @Override
    public void deleteWithdrawRecordListByIds(List<Long> ids) {
        // 删除
        withdrawRecordMapper.deleteByIds(ids);
    }


    private WithdrawRecordDO validateWithdrawRecordExists(Long id) {
        WithdrawRecordDO withdrawRecordDO = withdrawRecordMapper.selectById(id);
        if (withdrawRecordDO == null) {
            throw exception(WITHDRAW_RECORD_NOT_EXISTS);
        }
        return withdrawRecordDO;
    }

    @Override
    public WithdrawRecordDO getWithdrawRecord(Long id) {

        return validateWithdrawRecordExists(id);
    }

    @Override
    public PageResult<WithdrawRecordRespVO> getWithdrawRecordPage(WithdrawRecordPageReqVO pageReqVO) {
        return withdrawRecordMapper.selectPage(pageReqVO);
    }

    // ======================================== 其它接口 ========================================

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateTransferOrder(Long merchantTransferId, Long payTransferId) {
        // 1.1 校验提现单是否存在
        WithdrawRecordDO withdraw = withdrawRecordMapper.selectById(merchantTransferId);
        if (withdraw == null) {
            log.error("[updateBrokerageWithdrawTransferred][withdraw({}) payTransfer({}) 不存在提现单，请进行处理！]", merchantTransferId, payTransferId);
            throw exception(WITHDRAW_RECORD_NOT_EXISTS);
        }
        // 1.2 校验提现单已经结束（成功或失败）
        if (ObjectUtils.equalsAny(withdraw.getStatus(), WITHDRAW_SUCCESS.getStatus(), WITHDRAW_FAIL.getStatus())) {
            // 特殊：转账单编号相同，直接返回，说明重复回调
            if (ObjectUtil.equal(withdraw.getPayTransferId(), payTransferId)) {
                log.warn("[updateBrokerageWithdrawTransferred][withdraw({}) 已结束，且转账单编号相同({})，直接返回]", withdraw, payTransferId);
                return;
            }
            // 异常：转账单编号不同，说明转账单编号错误
            log.error("[updateBrokerageWithdrawTransferred][withdraw({}) 转账单不匹配({})，请进行处理！]", withdraw, payTransferId);
            throw exception(BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_ID_ERROR);
        }

        // 2. 校验转账单的合法性
        PayTransferRespDTO payTransfer = validateBrokerageTransferStatusCanUpdate(withdraw, payTransferId);

        // 3. 更新提现单状态
        Integer newStatus = PayTransferStatusEnum.isSuccess(payTransfer.getStatus()) ? WITHDRAW_SUCCESS.getStatus() :
                PayTransferStatusEnum.isClosed(payTransfer.getStatus()) ? WITHDRAW_FAIL.getStatus() : null;
        Assert.notNull(newStatus, "转账单状态({}) 不合法", payTransfer.getStatus());

        // 判断如果提现失败，则退回用户余额
        if (newStatus.equals(WITHDRAW_FAIL.getStatus())){
            // 退回余额
            coachWalletService.updateCoachWallet(withdraw.getCoachId(), true, withdraw.getAmount());
        }

        withdrawRecordMapper.update(Wrappers.<WithdrawRecordDO>lambdaUpdate()
                .eq(WithdrawRecordDO::getId, merchantTransferId)
                .set(WithdrawRecordDO::getStatus, newStatus)
                .set(WithdrawRecordDO::getDoneTime, payTransfer.getSuccessTime())
                .set(WithdrawRecordDO::getFailCause, payTransfer.getChannelErrorMsg()));
    }

    private PayTransferRespDTO validateBrokerageTransferStatusCanUpdate(WithdrawRecordDO withdraw, Long payTransferId) {
        // 1. 校验转账单是否存在
        PayTransferRespDTO payTransfer = payTransferApi.getTransfer(payTransferId);
        if (payTransfer == null) {
            log.error("[validateBrokerageTransferStatusCanUpdate][withdraw({}) payTransfer({}) 不存在，请进行处理！]", withdraw.getId(), payTransferId);
            throw exception(PAY_TRANSFER_NOT_FOUND);
        }

        // 2.1 校验转账单已成功或关闭
        if (!PayTransferStatusEnum.isSuccessOrClosed(payTransfer.getStatus())) {
            log.error("[validateBrokerageTransferStatusCanUpdate][withdraw({}) payTransfer({}) 未结束，请进行处理！payTransfer 数据是：{}]",
                    withdraw.getId(), payTransferId, JsonUtils.toJsonString(payTransfer));
            throw exception(BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_STATUS_NOT_SUCCESS_OR_CLOSED);
        }
        // 2.2 校验转账金额一致
        if (ObjectUtil.notEqual(payTransfer.getPrice(), withdraw.getAmount())) {
            log.error("[validateBrokerageTransferStatusCanUpdate][withdraw({}) payTransfer({}) 转账金额不匹配，请进行处理！withdraw 数据是：{}，payTransfer 数据是：{}]",
                    withdraw.getId(), payTransferId, JsonUtils.toJsonString(withdraw), JsonUtils.toJsonString(payTransfer));
            throw exception(BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_PRICE_NOT_MATCH);
        }
        // 2.3 校验转账订单匹配
        if (ObjectUtil.notEqual(payTransfer.getMerchantTransferId(), withdraw.getId().toString())) {
            log.error("[validateBrokerageTransferStatusCanUpdate][withdraw({}) 转账单不匹配({})，请进行处理！payTransfer 数据是：{}]",
                    withdraw.getId(), payTransferId, JsonUtils.toJsonString(payTransfer));
            throw exception(BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_MERCHANT_EXISTS);
        }
        return payTransfer;
    }

    // ======================================== APP 接口 ========================================

    @Override
    public void appUserWithdraw(BigDecimal amount, Long coachId) {
        // 转换单位
        Integer withdrawAmount = AmountUtil.yuanToCent(amount);
        // 扣除用户余额
        coachWalletService.updateCoachWallet(coachId, false, withdrawAmount);

        // 无报错，则代表扣除成功，则插入提现记录
        withdrawRecordMapper.insert(
                WithdrawRecordDO
                        .builder()
                        .coachId(coachId)
                        .amount(withdrawAmount)
                        .status(AUDITING.getStatus())
                        .build()
        );
    }

    @Override
    public PageResult<AppWithdrawRecordRespVO> appGetWithdrawRecordPage(PageParam pageParam, Long coachId) {
        return withdrawRecordMapper.appGetWithdrawRecordPage(pageParam, coachId);
    }
}